#include <iostream>
#include "ParametricCurves.h"
#include "GLWidget.h"
#include "Matrices.h"
#include "TestShapes.h"
#include "Materials.h"
#include "Exceptions.h"
#include "Cylinder.h"
#include "Toroid.h"
#include "MainWindow.h"

#define DISABLE_SHADERS

using namespace cagd;
using namespace std;

GLWidget::GLWidget(QWidget *parent): QGLWidget(parent)
{
    QGLFormat format;

    format.setDepth(true);
    format.setDoubleBuffer(true);
    format.setRgba(true);

    setFormat(format);

    _pc = 0;
    _dl = 0;
    _pl = 0;
    _sl = 0;
    _dl_enabled = true;
    _pl_enabled = false;
    _sl_enabled = false;
    _timer = 0;
    _actual_surface = 0;
    _actual_control_point_index = -1;
    _actula_control_points_pointer.resize(3);
    _texture= 0;

//    _timer = new QTimer(this);
//    _timer->setInterval(1);
//    connect(_timer, SIGNAL(timeout()), this, SLOT(animate()));
//    _timer->start();
}

GLWidget::~GLWidget()
{
    if (_pc != 0)
    {
        delete _pc;
        _pc = 0;
    }
    if (_dl != 0)
    {
        delete _dl;
        _dl = 0;
    }
    if (_pl != 0)
    {
        delete _pl;
        _pl = 0;
    }
    if (_sl != 0)
    {
        delete _sl;
        _sl = 0;
    }
    if (_actual_surface != 0)
    {
        delete _actual_surface;
        _actual_surface = 0;
    }
    if (_timer != 0)
    {
        delete _timer;
        _timer = 0;
    }
    //textura torlese
    if(_texture){
        glDeleteTextures(1,&_texture);
        _texture= 0;
    }
}

void GLWidget::initializeGL()
{
    glewInit();

    // defining the projection matrix
    _fovy = 45.0;
    _z_near = 1.0, _z_far = 1000.0;
    _aspect = (float)width() / (float) height();

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();    
    gluPerspective(_fovy, _aspect, _z_near, _z_far);

    // defining the modelview matrix
    _eye[0] = _eye[1] = 0.0, _eye[2] = 6.0;
    _center[0] = _center[1] = _center[2] = 0.0;
    _up[0] = _up[2] = 0.0, _up[1] = 1.0;

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt(_eye[0], _eye[1], _eye[2], _center[0], _center[1], _center[2], _up[0], _up[1], _up[2]);

    // setting initial values
    _x_angle = _y_angle = _z_angle = 0.0;
    _zoom = 1.0;

    // setting the clearing color to black
    glClearColor(0.0, 0.0, 0.0, 1.0);

    glEnable(GL_TEXTURE_2D);

    glEnable(GL_DEPTH_TEST);

    glEnable(GL_LIGHTING);
    glEnable(GL_NORMALIZE);

    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    // light
    // directional
    HCoordinate position_d(0.0, 0.0, 1.0, 0.0);
    Color       ambient_d (0.4, 0.4, 0.4, 0.1);
    Color       diffuse_d (0.8, 0.8, 0.8, 0.1);
    Color       specular_d(1.0, 1.0, 1.0, 0.1);

    _dl = new DirectionalLight(GL_LIGHT0, position_d, ambient_d, diffuse_d, specular_d);
    _dl->SwitchLight(true);

    // point
    HCoordinate position_p(0.0, 1.0, 1.0, 1.0);
    Color       ambient_p (0.174500, 0.011750, 0.011750, 1.0);
    Color       diffuse_p (0.614240, 0.041360, 0.041360, 1.0);
    Color       specular_p(0.727811, 0.626959, 0.626959, 1.0);

   _pl = new PointLight(GL_LIGHT1, position_p, ambient_p, diffuse_p, specular_p, 0.7f, 0.3f, 0.5f);

    // spot
    HCoordinate position_s  (0.0, 0.0, 0.2, 1.0);
    Color       ambient_s   (0.025, 0.125, 0.425, 1.0);
    Color       diffuse_s   (0.050, 0.250, 0.850, 1.0);
    Color       specular_s  (0.100, 0.500, 1.000, 1.0);
    HCoordinate direction_s (0.0, 0.0, -1.0, 1.0);

    _sl = new SpotLight(GL_LIGHT2, position_s, ambient_s, diffuse_s, specular_s, 1.0f, 0.1f, 0.2f, direction_s, 15.0f, 1.0f);


    // shader
#ifndef DISABLE_SHADERS
    try
    {
        _shader.CreateProgram(true);
        if (!_shader.AddVertexShader(0, "shaders/directional_light.vert", true) ||
            !_shader.AddFragmentShader(0, "shaders/directional_light.frag", true) ||
            !_shader.AddVertexShader(1, "shaders/two_sided_lighting.vert", true) ||
            !_shader.AddFragmentShader(1, "shaders/two_sided_lighting.frag", true) ||
            !_shader.AddVertexShader(2, "shaders/toon.vert", true) ||
            !_shader.AddFragmentShader(2, "shaders/toon.frag", true) ||
            !_shader.AttachVertexShader(0, true) ||
            !_shader.AttachFragmentShader(0, true) ||
            !_shader.LinkProgram(true))
        {
            throw Exception("An error appeared at the Shaders installation.");
        }
    }
    catch (Exception &e)
    {
        cerr << e << endl;
    }
#endif

//-------------------------------------------------------------------------------------

    // helix
//    RowMatrix<ptrCoordinateFunction> derivate(3);
//    derivate(0) = Helix;
//    derivate(1) = d1Helix;
//    derivate(2) = d2Helix;
//
//    _pc = new ParametricCurve(-5, 5, derivate, 100);
//    _pc->UpdateVertexBufferObjects();

//-------------------------------------------------------------------------------------

    // load modell
//    if (_mouse.LoadFromOFF("mouse.off", true))
//        if (_mouse.UpdateVertexBufferObjects(GL_DYNAMIC_DRAW))
//            _angle = 0.0;

//-------------------------------------------------------------------------------------

    // bicubic bezier or bspline patch
//    _patch.SetControlPoint(0, 0, -2.0, -2.0, 0.0);
//    _patch.SetControlPoint(0, 1, -2.0, -1.0, 0.0);
//    _patch.SetControlPoint(0, 2, -2.0, 1.0, 0.0);
//    _patch.SetControlPoint(0, 3, -2.0, 2.0, 0.0);
//
//    _patch.SetControlPoint(1, 0, -1.0, -2.0, 0.0);
//    _patch.SetControlPoint(1, 1, -1.0, -1.0, 2.0);
//    _patch.SetControlPoint(1, 2, -1.0, 1.0, 2.0);
//    _patch.SetControlPoint(1, 3, -1.0, 2.0, 0.0);
//
//    _patch.SetControlPoint(2, 0, 1.0, -2.0, 0.0);
//    _patch.SetControlPoint(2, 1, 1.0, -1.0, 2.0);
//    _patch.SetControlPoint(2, 2, 1.0, 1.0, 2.0);
//    _patch.SetControlPoint(2, 3, 1.0, 2.0, 0.0);
//
//    _patch.SetControlPoint(3, 0, 2.0, -2.0, 0.0);
//    _patch.SetControlPoint(3, 1, 2.0, -1.0, 0.0);
//    _patch.SetControlPoint(3, 2, 2.0, 1.0, 0.0);
//    _patch.SetControlPoint(3, 3, 2.0, 2.0, 0.0);
//
//    _patch.UpdateVBOOfControlNet();
//
//    if (_patch.GenerateMesh(30, 30, _before_interpolation))
//        _before_interpolation.UpdateVertexBufferObjects();
//
//    RowMatrix<GLdouble> u_knot_vector(4);
//    u_knot_vector(0) = 0.0;
//    u_knot_vector(1) = 1.0 / 3.0;
//    u_knot_vector(2) = 2.0 / 3.0;
//    u_knot_vector(3) = 1.0;
//
//    ColumnMatrix<GLdouble> v_knot_vector(4);
//    v_knot_vector(0) = 0.0;
//    v_knot_vector(1) = 1.0 / 3.0;
//    v_knot_vector(2) = 2.0 / 3.0;
//    v_knot_vector(3) = 1.0;
//
//    Matrix<DCoordinate> data_points(4, 4);
//    for (GLuint row = 0; row < 4; row++)
//        for (GLuint column = 0; column < 4; column++)
//            _patch.GetControlPoint(row, column, data_points(row, column));
//
//    if (_patch.UpdateControlPointsForInterpolation(u_knot_vector, v_knot_vector, data_points))
//        if (_patch.GenerateMesh(30, 30, _after_interpolation))
//            _after_interpolation.UpdateVertexBufferObjects();

//-------------------------------------------------------------------------
}

void GLWidget::paintGL()
{
    CTransformation CT;
    _camera.GetModelViewMatrix(CT);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    CT.Apply();

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // saving the current modelview matrix
    glPushMatrix();

        // rotating around the coordinate axes
        glRotatef(_x_angle, 1.0, 0.0, 0.0);
        glRotatef(_z_angle, 0.0, 0.0, 1.0);
        glRotatef(_y_angle, 0.0, 1.0, 0.0);

        // scaling
        glScalef(_zoom, _zoom, _zoom);

///////////////////////////////////////////////////////////////////////////////////////////////////////////

//        glColor3f(1.0, 0.0, 0.0);
//        pc->RenderCurvePoints();
//
//        glColor3f(0.0, 1.0, 0.0);
//        pc->RenderTangentVectors();
//
//        glColor3f(0.0, 0.0, 1.0);
//        pc->RenderAccelerationVectors();

//------------------------------------------------------------
        // light
        if (_dl && _dl_enabled)
            _dl->Enable();
        if (_pl && _pl_enabled)
            _pl->Enable();
        if (_sl && _sl_enabled)
            _sl->Enable();
        // shader
#ifndef DISABLE_SHADERS
        _shader.Enable(true);
#endif

//------------------------------------------------------------

        // model
//        MatFBRuby.Apply();
//        _mouse.Render();

//------------------------------------------------------------

        // patch
//        MatFBGold.Apply();
//        _patch.RenderControlNet(GL_LINE_STRIP);
//
//        MatFBRuby.Apply();
//        _before_interpolation.Render();
//
//        glEnable(GL_BLEND);
//        glDepthMask(GL_FALSE);
//        glBlendFunc(GL_SRC_ALPHA, GL_ONE);
//        MatFBTurquoise.Apply();
//        _after_interpolation.Render();
//        glDepthMask(GL_TRUE);
//        glDisable(GL_BLEND);

//----------------------------------------------------------------

        // general
        if (_actual_surface){
            if(_texture){
                glBindTexture(GL_TEXTURE_2D,_texture);
            }

            glEnable(GL_TEXTURE_2D);
            _actual_surface->Render(GL_TRIANGLES);
            glDisable(GL_TEXTURE_2D);
            MatFBGold.Apply();
            _actual_surface->RenderControlNet(GL_LINE_STRIP);
            if (_actual_control_point_index >= 0)
            {
                MatFBRuby.Apply();
                RenderAControlPoint();
            }
        }

//----------------------------------------------------------------
        // shader
#ifndef DISABLE_SHADERS
        _shader.Disable();
#endif
        // light
        if (_dl)
            _dl->Disable();
        if (_pl)
            _pl->Disable();
        if (_sl)
            _sl->Disable();
//----------------------------------------------------------------

    // restoring the former modelview matrix
    glPopMatrix();
}

void GLWidget::animate()
{
    GLfloat *vertex = _mouse.MapVertexBuffer(GL_READ_WRITE);
    GLfloat *normal = _mouse.MapNormalBuffer(GL_READ_ONLY);

    _angle += ONE_RADIAN;
    if (_angle >= TWO_PI) _angle -= TWO_PI;

    for (GLuint i = 0; i < _mouse.VertexCount(); i++)
    {
        GLfloat scale = sin(_angle) / 3000.0;
        for (GLuint coordinate = 0; coordinate < 3; coordinate++, vertex++, normal++)
            *vertex += scale * (*normal);
    }

    _mouse.UnmapVertexBuffer();
    _mouse.UnmapNormalBuffer();

    updateGL();
}

void GLWidget::resizeGL(int w, int h)
{
    // setting the new viewport
    glViewport(0, 0, w, h);

    // calculating the new aspect ratio
    _aspect = (float) w / (float) h;

    // updating the projection matrix
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(_fovy, _aspect, _z_near, _z_far);

    // reverting to the modelview matrix
    glMatrixMode(GL_MODELVIEW);

    // invoking the method paintGL
    updateGL();
}

// implementing the public slots
void GLWidget::set_x_angle(int angle)
{
    if (_x_angle != angle)
    {
        _x_angle = angle;
        updateGL();
    }
}

void GLWidget::set_y_angle(int angle)
{
    if (_y_angle != angle)
    {
        _y_angle = angle;
        updateGL();
    }
}

void GLWidget::set_z_angle(int angle)
{
    if (_z_angle != angle)
    {
        _z_angle = angle;
        updateGL();
    }
}

void GLWidget::set_scale_factor(double zoom)
{
    if (_zoom != zoom)
    {
        _zoom = zoom;
        updateGL();
    }
}

void GLWidget::enable_directional_light(bool enabled)
{
    if (enabled)
    {
        _shader.AttachVertexShader(0, true);
        _shader.AttachFragmentShader(0, true);
    }
    else
    {
        _shader.DettachVertexShader(0);
        _shader.DettachFragmentShader(0);
    }
    _shader.LinkProgram(true);
    updateGL();
}

void GLWidget::enable_two_sided_lighting(bool enabled)
{
    if (enabled)
    {
        _shader.AttachVertexShader(1, true);
        _shader.AttachFragmentShader(1, true);
    }
    else
    {
        _shader.DettachVertexShader(1);
        _shader.DettachFragmentShader(1);
    }
    _shader.LinkProgram(true);
    updateGL();
}

void GLWidget::enable_toon_light(bool enabled)
{
    if (enabled)
    {
        _shader.AttachVertexShader(2, true);
        _shader.AttachFragmentShader(2, true);
    }
    else
    {
        _shader.DettachVertexShader(2);
        _shader.DettachFragmentShader(2);
    }
    _shader.LinkProgram(true);
    updateGL();
}

void GLWidget::enable_light_source_0(bool enabled)
{
    _dl_enabled = enabled;
    _dl->SwitchLight(enabled);
    updateGL();
}

void GLWidget::enable_light_source_1(bool enabled)
{
    _pl_enabled = enabled;
    _pl->SwitchLight(enabled);
    updateGL();
}

void GLWidget::enable_light_source_2(bool enabled)
{
    _sl_enabled = enabled;
    _sl->SwitchLight(enabled);
    updateGL();
}

void GLWidget::CreateAndReplaceActualSurfaceObject(
        GLuint type_index,
        GLuint control_net_rows,
        GLuint control_net_cols,
        GLuint u_div_point_count,
        GLuint v_div_point_count,
        GLdouble param_0,
        GLdouble param_1,
        GLdouble param_2)
{
    delete _actual_surface;
    switch (type_index)
    {
    case 0:
        _actual_surface = new Cylinder(control_net_rows, control_net_cols, param_1, param_2, param_0, u_div_point_count, v_div_point_count);
        _actual_surface->BuildAllPatches(u_div_point_count, v_div_point_count);
        _actual_surface->UpdateVBOOfControlNet();
        break;
    case 1:
        _actual_surface = new Toroid(control_net_rows, control_net_cols, param_1, param_0, u_div_point_count, v_div_point_count);
        _actual_surface->BuildAllPatches(u_div_point_count, v_div_point_count);
        _actual_surface->UpdateVBOOfControlNet();
        break;
    case 2:
        _actual_surface = new Sphere(control_net_rows, control_net_cols, param_0, u_div_point_count, v_div_point_count);
        _actual_surface->BuildAllPatches(u_div_point_count, v_div_point_count);
        _actual_surface->UpdateVBOOfControlNet();
        break;
    }
    updateGL();
}

void GLWidget::Walk(float step)
{
    _camera.Walk(step);
    updateGL();
}
void GLWidget::Strafe(float step)
{
    _camera.Strafe(step);
    updateGL();
}
void GLWidget::Heighten(float step)
{
    _camera.Heighten(step);
    updateGL();
}
void GLWidget::Roll(float step)
{
    _camera.Roll(step * ROTATION_UNIT);
    updateGL();
}
void GLWidget::Pitch(float step)
{
    _camera.Pitch(step * ROTATION_UNIT);
    updateGL();
}
void GLWidget::Yaw(float step)
{
    _camera.Yaw(step * ROTATION_UNIT);
    updateGL();
}
// mouse events ------------------------------------------------------------
void GLWidget::mousePressEvent(QMouseEvent *e)
{
//    GLdouble x = e->posF().x();
//    GLdouble y = this->size().height() - 1 - e->posF().y();
//
//    GLdouble model_view[16];
//    GLdouble proj_view[16];
//    GLint view_port[4];
//
//    glGetDoublev(GL_MODELVIEW_MATRIX, &model_view[0]);
//    glGetDoublev(GL_PROJECTION_MATRIX, &proj_view[0]);
//    glGetIntegerv(GL_VIEWPORT, &view_port[0]);
//
//    GLdouble obj_x, obj_y, obj_z;
//
//    if (gluUnProject(x, y, 0.0, &model_view[0], &proj_view[0], &view_port[0], &obj_x, &obj_y, &obj_z))
//    {
//        int i = 1;
//    }
//    int j = 1;
}
void GLWidget::mouseMoveEvent(QMouseEvent *e)
{}

// control point manipulation -------------------------------------------------
GLboolean GLWidget::ControlPointManipulation_enable(bool enabled)
{
    if (!_actual_surface)
        return false;

    if (enabled)
    {
        _actual_control_point_index = 0;
        _actual_surface->GetControlPoint(0, _actual_control_point);
        UpdateControlPointsPointer();
    }
    else
    {
        _actual_control_point_index = -1;
    }
    updateGL();

    return true;
}
void GLWidget::ControlPointIndex_changed(int index)
{
    _actual_control_point_index = index;
    _actual_surface->GetControlPoint(index, _actual_control_point);
    UpdateControlPointsPointer();
    updateGL();
}
void GLWidget::ControlPointManipulation_accepted()
{
    _actual_surface->SetControlPoint(_actual_control_point_index, _actual_control_point, true);
    updateGL();
}
void GLWidget::ControlPointManipulation_rejected()
{
}
void GLWidget::ControlPoint_x_changed(double x)
{
    _actual_control_point.x = x;
    _actual_surface->SetControlPoint(_actual_control_point_index, _actual_control_point, false);
    _actual_surface->UpdateVBOOfControlNet();
    UpdateControlPointsPointer();
    //real-time
    _actual_surface->SetControlPoint(_actual_control_point_index, _actual_control_point, true);
    updateGL();
}
void GLWidget::ControlPoint_y_changed(double y)
{
    _actual_control_point.y = y;
    _actual_surface->SetControlPoint(_actual_control_point_index, _actual_control_point, false);
    _actual_surface->UpdateVBOOfControlNet();
    UpdateControlPointsPointer();
    //real-time
    _actual_surface->SetControlPoint(_actual_control_point_index, _actual_control_point, true);
    updateGL();
}
void GLWidget::ControlPoint_z_changed(double z)
{
    _actual_control_point.z = z;
    _actual_surface->SetControlPoint(_actual_control_point_index, _actual_control_point, false);
    _actual_surface->UpdateVBOOfControlNet();
    UpdateControlPointsPointer();
    //real-time
    _actual_surface->SetControlPoint(_actual_control_point_index, _actual_control_point, true);
    updateGL();
}

GLboolean GLWidget::UpdateControlPointsPointer()
{
    _actula_control_points_pointer[0] = DCoordinate(_actual_control_point.x - 0.02, _actual_control_point.y - 0.02, _actual_control_point.z);
    _actula_control_points_pointer[1] = DCoordinate(_actual_control_point.x + 0.02, _actual_control_point.y - 0.02, _actual_control_point.z);
    _actula_control_points_pointer[2] = DCoordinate(_actual_control_point.x, _actual_control_point.y + 0.04472, _actual_control_point.z);
    return true;
}
GLboolean GLWidget::RenderAControlPoint()
{
    glBegin(GL_TRIANGLES);
        glVertex3f(_actula_control_points_pointer[0].x,
                   _actula_control_points_pointer[0].y,
                   _actula_control_points_pointer[0].z);

        glVertex3f(_actula_control_points_pointer[1].x,
                   _actula_control_points_pointer[1].y,
                   _actula_control_points_pointer[1].z);

        glVertex3f(_actula_control_points_pointer[2].x,
                   _actula_control_points_pointer[2].y,
                   _actula_control_points_pointer[2].z);
    glEnd();
    return true;
}

void GLWidget::GetControlPointsInformation(GLuint &point_count, GLdouble &x, GLdouble &y, GLdouble &z)
{
    point_count = _actual_surface->GetControlPointCount();
    _actual_surface->GetControlPoint(_actual_control_point_index, x, y, z);
}

void GLWidget::LoadTexture_button(){
    GLenum texture_format;
    GLint  n_colors;

    //printf("LOAD\n");
    QString fileName = QFileDialog::getOpenFileName(this,tr("Open File"), QDir::currentPath());
    if (!fileName.isEmpty()) {
        QImage image(fileName);
        if (image.isNull()) {
            QMessageBox::information(this, tr("Image Viewer"),tr("Cannot load %1.").arg(fileName));
            return;
        }

        QImage texture= QGLWidget::convertToGLFormat(image);

        //a szincsatornak szama
        if(texture.hasAlphaChannel()){
            texture_format= GL_RGBA;
            n_colors= 4;
        }else{
            texture_format= GL_RGB;
            n_colors= 3;
        }


        /*if(n_colors== 4){//van alfacsatorna
         if( surface->format->Rmask== 0x000000ff){
          texture_format= GL_RGBA;
         }else{
          texture_format= GL_BGRA;
         }
        }else{
         if(n_colors == 3){//nincs alfa csatona
          if(surface->format->Rmask== 0x000000ff){
           texture_format= GL_RGB;
          }else{
           texture_format= GL_BGR;
          }
         }else{
          printf("WARNING: not truecolor\n");
          return 0;
         }
        }

        //printf("%d %d %d\n",n_colors,surface->w,surface->h);*/

        if(_texture){
            glDeleteTextures(1,&_texture);
            _texture= 0;
        }

        glGenTextures(1,&_texture);
        glBindTexture(GL_TEXTURE_2D,_texture);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
        glTexParameteri(GL_TEXTURE_2D,GL_GENERATE_MIPMAP,GL_TRUE);
        glTexImage2D(GL_TEXTURE_2D,0,n_colors,texture.width(),texture.height(),0,texture_format,GL_UNSIGNED_BYTE,texture.bits());

        printf("%d\n",n_colors);
        printf("%d\n",texture.width());
        printf("%d\n",texture.height());


        updateGL();

        /*imageLabel->setPixmap(QPixmap::fromImage(image));
        scaleFactor = 1.0;

        printAct->setEnabled(true);
        fitToWindowAct->setEnabled(true);
        updateActions();

        if (!fitToWindowAct->isChecked())
            imageLabel->adjustSize();*/
    }
}
